package com.peking.donations.plugins.oauth2.client;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 如果有注解@EnableGitHubOAuth2 手动置入GitHub oauth2 相关spring-security 配置
 * 
 * @author Vigor Yuan
 */
@Configuration
@Conditional(OAuth2Configuration.WebSecurityEnhancerCondition.class)
public class OAuth2Configuration implements ImportAware, BeanPostProcessor, BeanFactoryAware {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private Class<?> configType;

    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (this.configType.isAssignableFrom(bean.getClass()) && bean instanceof WebSecurityConfigurerAdapter) {
            logger.debug("postProcessAfterInitialization github configurer init");
            EnableCustomerOAuth2 annotation = AnnotationUtils.findAnnotation(bean.getClass(),
                    EnableCustomerOAuth2.class);
            String[] config = annotation.config();
            ProxyFactory factory = new ProxyFactory();
            factory.setTarget(bean);
            factory.addAdvice(new SsoSecurityAdapter(this.beanFactory, config));
            bean = factory.getProxy();
        }
        return bean;
    }

    @Override
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        this.configType = ClassUtils.resolveClassName(importMetadata.getClassName(), null);
    }

    /**
     * 拦截WebSecurityConfigurerAdapter.init() 方法 置入自定义配置
     */
    private static class SsoSecurityAdapter implements MethodInterceptor {

        private List<OAuth2Configurer> configurerList = new ArrayList<>();

        SsoSecurityAdapter(BeanFactory beanFactory, String[] config) {
            for (String configName : config) {
                OAuth2Configurer configurer = new OAuth2Configurer(beanFactory, configName);
                this.configurerList.add(configurer);
            }
        }

        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            if (invocation.getMethod().getName().equals("init")) {
                Method method = ReflectionUtils.findMethod(WebSecurityConfigurerAdapter.class, "getHttp");
                ReflectionUtils.makeAccessible(method);
                HttpSecurity http = (HttpSecurity) ReflectionUtils.invokeMethod(method, invocation.getThis());

                for (OAuth2Configurer oAuth2Configurer : configurerList) {
                    oAuth2Configurer.configure(http);
                }
            }
            return invocation.proceed();
        }

    }

    protected static class WebSecurityEnhancerCondition extends SpringBootCondition {

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            String[] enablers = context.getBeanFactory().getBeanNamesForAnnotation(EnableCustomerOAuth2.class);
            for (String name : enablers) {
                if (context.getBeanFactory().isTypeMatch(name, WebSecurityConfigurerAdapter.class)) {
                    return ConditionOutcome.match("found @EnableCustomerOAuth2 on a WebSecurityConfigurerAdapter");
                }
            }
            return ConditionOutcome.noMatch("found no @EnableCustomerOAuth2 on a WebSecurityConfigurerAdapter");
        }

    }
}
